package nodes

import (
	"awesome-devops/applications/models"
	"awesome-devops/applications/tools"
	"awesome-devops/boot"
	"gitee.com/jjawesomejj/awesome-util/config"
	"gitee.com/jjawesomejj/awesome-util/helper/commonhelper"
	"gitee.com/jjawesomejj/awesome-util/helper/timeHelper"
	"sort"
	"strconv"
	"sync"
	"time"
)

var nodeManager NodeManager

type NodeDescribe struct {
	Runner          *models.Runner
	RunnerInfo      models.NodeInfo
	LastRequestTime time.Time
	Error           error
	IsOnline        bool
}

//NodeScore 计算某个节点的分数
func (node NodeDescribe) NodeScore() int {
	if !node.IsOnline {
		return 0
	}
	return int(node.RunnerInfo.CpuSurplus) * int(node.RunnerInfo.SurplusMemory)
}

type NodeManager struct {
	Runner sync.Map
}

func (manager *NodeManager) InitRunnerFromDataBase() {
	var nodes []models.Runner
	res := boot.GetDbConnection().Where("delete_time", 0).
		Table((&models.Runner{}).TableName()).Find(&nodes)
	if res != nil && res.Error != nil {
		tools.GetLogger().Error("加载节点失败" + res.Error.Error())
		nodes = make([]models.Runner, 0)
	}
	grpcPortString := config.GetConfigByKey("rpcPort", func() interface{} {
		return "8787"
	}).(string)
	grpcPortPort, err := strconv.Atoi(grpcPortString)
	if err != nil {
		panic("rpcPort like string '8383'")
	}
	portString := config.GetConfigByKey("httpPort", func() interface{} {
		return "80"
	}).(string)
	portInt, err := strconv.Atoi(portString)
	if err != nil {
		panic("httpPort need string params")
	}
	virtualNode := models.Runner{
		Name:     "system auto create runner run in master",
		RpcPort:  grpcPortPort,
		WsPort:   portInt,
		UniqueId: config.GetConfigByKey("unique_id").(string),
		Ip:       "127.0.0.1",
		ID:       0,
	}
	for index, node := range nodes {
		if node.UniqueId != "" && node.UniqueId == virtualNode.UniqueId {
			nodes = append(nodes[0:index], nodes[index+1:]...)
			virtualNode.Labels = node.Labels
		}
	}
	//加载本机节点
	nodes = append([]models.Runner{virtualNode}, nodes...)
	group := sync.WaitGroup{}
	currentNodeIds := map[uint]interface{}{}
	for _, node := range nodes {
		currentNodeIds[node.ID] = node
		node := node
		group.Add(1)
		go func() {
			nodeDescribe := manager.initNode(&node)
			manager.Runner.Store(node.ID, nodeDescribe)
			group.Done()
		}()
	}
	group.Wait()
	length := 0
	manager.Runner.Range(func(key, value interface{}) bool {
		length += 1
		if _, ok := currentNodeIds[key.(uint)]; !ok {
			manager.Runner.Delete(key.(uint))
		}
		return true
	})
	//manager.PrintfRunner()
}

//func (manager *NodeManager)InitNodeWs()  {
//	wsClient:=awesomehttp.HttpBaseHandler{}
//}

func (manager *NodeManager) RangerRunner(fun func(describe *NodeDescribe)) {
	manager.Runner.Range(func(key, value interface{}) bool {
		fun(value.(*NodeDescribe))
		return true
	})
}

func (manager *NodeManager) PrintfRunner() {
	outPrintJson := make([]map[string]string, 0)
	manager.Runner.Range(func(key, value interface{}) bool {
		nodeDesc := value.(*NodeDescribe)
		alive := "否"
		if nodeDesc.IsOnline {
			alive = "是"
		}
		outPrintJson = append(outPrintJson, map[string]string{
			"id":                strconv.Itoa(int(nodeDesc.Runner.ID)),
			"name":              nodeDesc.Runner.Name,
			"ip":                nodeDesc.Runner.Ip,
			"rpc_port":          strconv.Itoa(nodeDesc.Runner.RpcPort),
			"ws_port":           strconv.Itoa(nodeDesc.Runner.WsPort),
			"alive":             alive,
			"last_request_time": timeHelper.TimeParseString(nodeDesc.LastRequestTime),
			"resource":          commonhelper.JsonEncode(nodeDesc.RunnerInfo),
		})
		return true
	})
	//table, err := commonhelper.PrintTable(outPrintJson, false)
	//if err == nil {
	//	echo.Blue(table)
	//}
}

/**
GetRunner 获取合适的执行节点
*/
func (manager *NodeManager) GetRunner(deployId int) (error, []*NodeDescribe) {
	deployModel := models.Deploy{}
	err := deployModel.RowByID(deployId, &deployModel)
	if err != nil {
		return err, make([]*NodeDescribe, 0)
	}
	project := models.Project{}
	err = project.RowByID(deployModel.ProjectId, &project)
	if err != nil {
		return err, make([]*NodeDescribe, 0)
	}
	projectLabel := project.GetRunnerLabels()
	canChooseNodes := make([]map[string]interface{}, 0)
	manager.RangerRunner(func(describe *NodeDescribe) {
		if describe.Runner.CheckLabelIsMatch(projectLabel) {
			score := describe.NodeScore()
			if score > 0 {
				canChooseNodes = append(canChooseNodes, map[string]interface{}{
					"node":  describe,
					"score": describe.NodeScore(),
				})
			}

		}
	})
	result := make([]*NodeDescribe, 0)
	sortByScore(canChooseNodes, "score")
	for _, describe := range canChooseNodes {
		result = append(result, describe["node"].(*NodeDescribe))
	}
	return nil, result
}

func sortByScore(nodes []map[string]interface{}, key string) []map[string]interface{} {
	sort.Slice(nodes, func(i, j int) bool {
		return nodes[i][key].(int) < nodes[j][key].(int)
	})
	return nodes
}

func (manager *NodeManager) initNode(runner *models.Runner) *NodeDescribe {
	node := NodeDescribe{}
	nodeInfo, err := runner.GetNodeRunnerResource()
	if err != nil {
		node.Error = err
		node.IsOnline = false
	} else {
		node.RunnerInfo = nodeInfo
		node.IsOnline = true
	}
	node.LastRequestTime = time.Now()
	node.Runner = runner
	return &node
}

func Start() {
	nodeManager = NodeManager{}
	c := time.NewTicker(time.Second * 10)
	nodeManager.InitRunnerFromDataBase()
	for true {
		select {
		case _ = <-c.C:
			nodeManager.InitRunnerFromDataBase()
		}
	}
}
func GetNodeManager() *NodeManager {
	return &nodeManager
}
